/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "VideoDec.h"
#include <hilog/log.h>
#include <multimedia/player_framework/native_avcapability.h>
#include "compressVideo/deMuxer/DeMuxer.h"
#include "compressVideo/encoder/VideoEnc.h"
#include "compressVideo/compressTools/Tools.h"
using namespace std;

#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x3200
#define LOG_TAG "videoCompressor"

VideoDec::~VideoDec() 
{ 
    Release(); 
}

namespace {
VideoDec *g_decSample = nullptr;
void VdecError(OH_AVCodec *codec, int32_t errorCode, void *userData)
{ 
    g_decSample->Release(); 
}

void VdecFormatChanged(OH_AVCodec *codec, OH_AVFormat *format, void *userData) 
{
    int32_t current_width = 0;
    int32_t current_height = 0;
    OH_AVFormat_GetIntValue(format, OH_MD_KEY_WIDTH, &current_width);
    OH_AVFormat_GetIntValue(format, OH_MD_KEY_HEIGHT, &current_height);
    g_decSample->width = current_width;
    g_decSample->height = current_height;
}

void VdecInputDataReady(OH_AVCodec *codec, uint32_t index, OH_AVMemory *data, void *userData) 
{
    VDecSignal *signal = static_cast<VDecSignal *>(userData);
    unique_lock<mutex> lock(signal->inMutex_);
    signal->inIdxQueue_.push(index);
    signal->inBufferQueue_.push(data);
    signal->inCond_.notify_all();
}

void VdecOutputDataReady(OH_AVCodec *codec, uint32_t index, OH_AVMemory *data, OH_AVCodecBufferAttr *attr,
                         void *userData) 
{
    VDecSignal *signal = static_cast<VDecSignal *>(userData);
    unique_lock<mutex> lock(signal->outMutex_);
    signal->outIdxQueue_.push(index);
    if (!attr) {
        OH_LOG_ERROR(LOG_APP, "compressTrace|VdecOutputDataReady attr is null");
    } else {
        signal->attrQueue_.push(*attr);
    }
    signal->outCond_.notify_all();
}
} // namespace

void VideoDec::RegisterDeMuxer(DeMuxer *dm) 
{ 
    deMuxer = dm; 
}


int32_t VideoDec::ConfigureVideoDecoder() 
{
    OH_AVFormat *format = OH_AVFormat_Create();
    if (format == nullptr) {
        OH_LOG_ERROR(LOG_APP, "compressTrace|Failed to create videoDecode format");
        return AV_ERR_UNKNOWN;
    }
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_WIDTH, width);
    OH_AVFormat_SetIntValue(format, OH_MD_KEY_HEIGHT, height);
    OH_AVCapability *cap = OH_AVCodec_GetCapability(videoMime.data(), false);
    std::string codecName = OH_AVCapability_GetName(cap);
    if (codecName.find("rk") == string::npos) {
        OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, AV_PIXEL_FORMAT_SURFACE_FORMAT);
    } else {
        OH_LOG_ERROR(LOG_APP, "compressTrace|ConfigureVideoDecoder g_codecName no npos");
        OH_AVFormat_SetIntValue(format, OH_MD_KEY_PIXEL_FORMAT, AV_PIXEL_FORMAT_RGBA);
    }
    OH_AVFormat_SetDoubleValue(format, OH_MD_KEY_FRAME_RATE, defaultFrameRate);
    int ret = OH_VideoDecoder_Configure(vdec_, format);
    OH_LOG_ERROR(LOG_APP, "compressTrace|BuildVideoDeCoder  宽度为:%{public}d 高度为 %{public}d 帧率:%{public}f", width, height, defaultFrameRate);
    OH_AVFormat_Destroy(format);
    return ret;
}

int32_t VideoDec::SetOutputSurface() 
{ 
    return OH_VideoDecoder_SetSurface(vdec_, mutexManager->nativeWindow); 
}

int32_t VideoDec::RunVideoDec(std::string codeName) 
{
    int err = CreateVideoDecoder(codeName);
    if (err != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "compressTrace|Failed to create video decoder");
        Release();
        return err;
    }

    err = ConfigureVideoDecoder();
    if (err != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "compressTrace|Failed to configure video decoder");
        Release();
        return err;
    }

    err = SetOutputSurface();
    if (err != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "compressTrace|Failed to video SetOutputSurface");
        Release();
        return err;
    }

    err = SetVideoDecoderCallback();
    if (err != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "compressTrace|Failed to video SetVideoDecoderCallback");
        Release();
        return err;
    }

    err = StartVideoDecoder();
    if (err != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "compressTrace|videoCompressor failed to start video decoder");
        Release();
        return err;
    }
    return err;
}

int32_t VideoDec::SetVideoDecoderCallback() 
{
    signal_ = make_unique<VDecSignal>();
    if (signal_ == nullptr) {
        OH_LOG_ERROR(LOG_APP, "compressTrace|Failed to new VDecSignal");
        return AV_ERR_UNKNOWN;
    }

    cb_.onError = VdecError;
    cb_.onStreamChanged = VdecFormatChanged;
    cb_.onNeedInputData = VdecInputDataReady;
    cb_.onNeedOutputData = VdecOutputDataReady;
    return OH_VideoDecoder_SetCallback(vdec_, cb_, static_cast<void *>(signal_.get()));
}

int32_t VideoDec::CreateVideoDecoder(std::string codeName) {
    vdec_ = OH_VideoDecoder_CreateByMime(codeName.data());
    g_decSample = this;
    return vdec_ == nullptr ? AV_ERR_UNKNOWN : AV_ERR_OK;
}

int32_t VideoDec::StartVideoDecoder()
{
    isRunning_.store(true);
    int ret = OH_VideoDecoder_Start(vdec_);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "compressTrace|Failed to start codec");
        isRunning_.store(false);
        Release();
        return ret;
    }

    inputLoop_ = make_unique<thread>(&VideoDec::InputFunc, this);

    if (inputLoop_ == nullptr) {
        OH_LOG_ERROR(LOG_APP, "compressTrace|Failed to decode create input loop");
        isRunning_.store(false);
        OH_VideoDecoder_Stop(vdec_);
        return AV_ERR_UNKNOWN;
    }

    outputLoop_ = make_unique<thread>(&VideoDec::OutputFunc, this);
    if (outputLoop_ == nullptr) {
        OH_LOG_ERROR(LOG_APP, "compressTrace|Failed to decode create output loop");
        isRunning_.store(false);
        OH_VideoDecoder_Stop(vdec_);
        Release();
        return AV_ERR_UNKNOWN;
    }
    return AV_ERR_OK;
}

void VideoDec::WaitForEos() 
{
    if (inputLoop_ && inputLoop_->joinable()) {
        inputLoop_->join();
    }

    if (outputLoop_ && outputLoop_->joinable()) {
        outputLoop_->join();
    }
    outputLoop_ = nullptr;
    inputLoop_ = nullptr;
}

void VideoDec::setProgress(ProgressTools* compressParams)
{
    if (compressParams) {
        compressParams_ = compressParams;
    }    
}

void VideoDec::InputFunc() 
{
    uint32_t frameCount_ = 0;
    uint32_t errCount = 0;
    while (true) {
        if (!isRunning_.load()) {
            break;
        }
        unique_lock<mutex> lock(signal_->inMutex_);
        signal_->inCond_.wait(lock, [this]() { return signal_->inIdxQueue_.size() > 0; });
        if (!isRunning_.load()) {
            break;
        }
        uint32_t index = signal_->inIdxQueue_.front();
        auto buffer = signal_->inBufferQueue_.front();
        OH_AVCodecBufferAttr attr;
        OH_AVDemuxer_ReadSample(deMuxer->deMuxer, mutexManager->vTrackId, buffer, &attr);
        if (compressParams_) {
            compressParams_->currentOffset += attr.size;
            compressParams_->currentProgress = (static_cast<float>(compressParams_->currentOffset) / compressParams_->inputSize * 100);
        }

        if (attr.flags == AVCODEC_BUFFER_FLAGS_EOS) {
            if (compressParams_) {
                compressParams_->currentProgress = 100;
            }
            EndInput(attr, index, frameCount_);
            break;
        }
        if (OH_VideoDecoder_PushInputData(vdec_, index, attr) != AV_ERR_OK) {
            errCount++;
            OH_LOG_ERROR(LOG_APP, "compressTrace|video push input data failed error");
        }
        frameCount_++;
        signal_->inIdxQueue_.pop();
        signal_->inBufferQueue_.pop();
    }
}

void VideoDec::EndInput(OH_AVCodecBufferAttr attr, int index, int frameCount) 
{
    attr.pts = 0;
    attr.size = 0;
    attr.offset = 0;
    OH_VideoDecoder_PushInputData(vdec_, index, attr);
}

void VideoDec::OutputFunc() 
{
    uint32_t errCount = 0;
    uint32_t enCount = 0;
    while (true) {
        if (!isRunning_.load()) {
            break;
        }
        unique_lock<mutex> lock(signal_->outMutex_);
        signal_->outCond_.wait(lock, [this]() { return signal_->outIdxQueue_.size() > 0; });
        if (!isRunning_.load()) {
            break;
        }
        uint32_t index = signal_->outIdxQueue_.front();
        OH_AVCodecBufferAttr attr = signal_->attrQueue_.front();
        if (attr.flags == AVCODEC_BUFFER_FLAGS_EOS) {
            VideoEnc->SendEncEos();
            break;
        }
        if (OH_VideoDecoder_RenderOutputData(vdec_, index) != AV_ERR_OK) {
            OH_LOG_ERROR(LOG_APP, "compressTrace|videoDecode OH_VideoDecoder_RenderOutputData error");
            errCount = errCount + 1;
        }

        if (errCount > 0) {
            OH_LOG_ERROR(LOG_APP, "compressTrace|videoDecode errCount > 0");
            break;
        }
        signal_->outIdxQueue_.pop();
        signal_->attrQueue_.pop();
        enCount++;
    }
}

void VideoDec::RegisterMuxerManager(MutexManager *mutex) 
{ 
    mutexManager = mutex; 
}

int32_t VideoDec::Release() 
{
    if (vdec_ != nullptr) {
        OH_VideoDecoder_Stop(vdec_);
        OH_VideoDecoder_Flush(vdec_);
        int ret = OH_VideoDecoder_Destroy(vdec_);
        vdec_ = nullptr;
        if (signal_ != nullptr) {
            signal_ = nullptr;
        }
        return ret;
    }
    StopInLoop();
    StopOutLoop();
    return AV_ERR_OK;
}

void VideoDec::StopInLoop() 
{
    if (inputLoop_ != nullptr && inputLoop_->joinable()) {
        unique_lock<mutex> lock(signal_->inMutex_);
        Tools::ClearIntQueue(signal_->inIdxQueue_);
        Tools::ClearMemoryBufferQueue(signal_->inBufferQueue_);
        signal_->inCond_.notify_all();
        lock.unlock();
        inputLoop_->join();
    }
}

void VideoDec::StopOutLoop() 
{
    if (outputLoop_ != nullptr && outputLoop_->joinable()) {
        unique_lock<mutex> lock(signal_->outMutex_);
        Tools::ClearIntQueue(signal_->outIdxQueue_);
        Tools::ClearBufferQueue(signal_->attrQueue_);
        signal_->outCond_.notify_all();
        lock.unlock();
        outputLoop_->join();
    }
}